#include <rtthread.h>
#include <rtdevice.h>

static void msg_complete(struct urb *r)
{
    rt_completion_done((struct rt_completion*)r->context);
}

static int _wait_submit_complete(struct uinstance *dev, struct upipe *pipe,
                                 struct urb *r, void *buf, int size, int timeout)
{
    int ret;
    struct rt_completion comp;

    rt_completion_init(&comp);

    r->context = &comp;
    r->buf = buf;
    r->size = size;

    ret = rt_usbh_urb_submit(dev, pipe, r);
    if (ret != 0)
        goto _out;

    if (rt_completion_wait(&comp, rt_tick_from_millisecond(timeout)) != 0)
    {
        ret = -ETIMEDOUT;
        goto _out;
    }
    else
    {
        ret = r->actual_size;
    }

_out:
    return ret;       
}

int rt_usbh_control_msg(struct uinstance *dev, struct urequest *setup, void *data, int size, int timeout)
{
    int ret;
    struct urb *r;
    struct upipe *pipe;
    int status;

    r = rt_usbh_urb_alloc(0, msg_complete);
    if (!r)
        return -ENOMEM;

    rt_thread_mdelay(10);
    /* SETUP stage */
    r->is_setup = 1;
    ret = _wait_submit_complete(dev, dev->pipe_ep0_out,
                                r, setup, sizeof(*setup), timeout);
    if (ret != sizeof(*setup))
    {
        goto _out;
    }

    rt_thread_mdelay(20);

    /* DATA stage */
    ret = 0;
    r->is_setup = 0;
    if (data && size)
    {
        if (setup->request_type & USB_REQ_TYPE_DIR_IN)
            pipe = dev->pipe_ep0_in;
        else
            pipe = dev->pipe_ep0_out;

        ret = _wait_submit_complete(dev, pipe, r,
                                    data, size, timeout);
        if (ret <= 0)
            goto _out;
    }

    /* STATUS stage */
    if (setup->request_type & USB_REQ_TYPE_DIR_IN)
        pipe = dev->pipe_ep0_out;
    else
        pipe = dev->pipe_ep0_in;

    rt_thread_mdelay(20);

    status = _wait_submit_complete(dev, pipe, r, RT_NULL, 0, timeout);
    if (status != 0)
        ret = status;

_out:
    rt_usbh_urb_free(r);

    return ret;
}
